Skip to content

feat(web): add Tools tab and row address type display setting#373

Merged
ChuckBuilds merged 5 commits into
mainfrom
feat/tools-tab-and-row-address-type
Jun 29, 2026
Merged

feat(web): add Tools tab and row address type display setting#373
ChuckBuilds merged 5 commits into
mainfrom
feat/tools-tab-and-row-address-type

Conversation

@ChuckBuilds

@ChuckBuilds ChuckBuilds commented Jun 16, 2026

Copy link
Copy Markdown
Owner

Summary

  • Tools tab — new maintenance tab in the web UI with one-click operations that previously required SSH access
  • Row address type — exposes the hzeller row_address_type option (0–4) in the Display settings tab; the backend already consumed this value from config but there was no UI or API handling for it

Tools tab features

Section Buttons
Git & Updates Live git status panel (branch, dirty/clean badge, recent commits); Pull Latest (stash + rebase); Force Reset to origin/main (with inline confirmation)
Python Dependencies Reinstall base `requirements.txt`; Reinstall per-plugin requirements (shows pass/fail per plugin with expandable pip output)
Maintenance Clear `pycache` directories
Services Restart display service; Restart web interface

Row address type

Added a dropdown to the Display → Hardware Configuration section with all five hzeller options:

  • `0` — Default
  • `1` — AB-addressed panels
  • `2` — Row direct
  • `3` — ABC-addressed panels
  • `4` — ABC Shift + DE direct

`display_manager.py` already read `hardware_config.get('row_address_type', 0)` — this PR wires up the missing UI field, API field list, and 0–4 range validation.

Files changed

  • `web_interface/templates/v3/partials/tools.html` — new partial
  • `web_interface/templates/v3/base.html` — Tools nav tab + content div
  • `web_interface/blueprints/pages_v3.py` — partial route + loader
  • `web_interface/blueprints/api_v3.py` — new actions (`install_base_requirements`, `install_plugin_requirements`, `force_git_reset`, `clear_pycache`), new `GET /api/v3/system/git-info` endpoint, `row_address_type` field + validation
  • `web_interface/templates/v3/partials/display.html` — row address type dropdown

Test plan

  • Tools tab appears in nav and loads without errors
  • Git status panel shows correct branch and recent commits
  • Pull Latest and Force Reset buttons work (Force Reset requires inline confirmation click)
  • Reinstall base requirements shows pip output
  • Reinstall plugin requirements shows per-plugin results
  • Clear cache reports number of directories removed
  • Row address type dropdown saves and reloads correctly
  • Invalid row_address_type values (e.g. 5) are rejected with 400

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added a new Tools tab with utilities for updates, dependency installs, maintenance cleanup, and service restarts.
    • Added Git status details in the Tools view, including branch, recent commits, and remote information.
    • Added a new setting for Row Address Type in Display hardware configuration.
  • Bug Fixes

    • Improved handling of display settings so the new row address option is saved correctly.
    • Enhanced error handling when loading the new Tools section.

@coderabbitai

coderabbitai Bot commented Jun 16, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@ChuckBuilds, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 37 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 64d525a0-fa51-49cf-969c-a2e31dd69e24

📥 Commits

Reviewing files that changed from the base of the PR and between 06ea4aa and f40e6f3.

📒 Files selected for processing (2)
  • web_interface/blueprints/api_v3.py
  • web_interface/templates/v3/partials/tools.html
📝 Walkthrough

Walkthrough

Adds a row_address_type field (0–4) to the display hardware config with validation and a matching select UI. Introduces four new system action API endpoints (pip installs, git reset, pycache clear) and a GET /system/git-info endpoint with credential scrubbing. Wires a new Tools tab into the v3 interface via a partial template and page route.

Changes

Tools Tab, System Actions, and Row Address Type

Layer / File(s) Summary
Row address type display config
web_interface/blueprints/api_v3.py, web_interface/templates/v3/partials/display.html
save_main_config adds row_address_type to recognized display fields, validates it as an integer 0–4, and coerces it on write. The display partial adds a matching <select> control bound to main_config.display.hardware.row_address_type.
System actions and git-info API
web_interface/blueprints/api_v3.py
Adds urllib.parse import; adds _truncate_output and _scrub_git_remote_url helpers. Extends execute_system_action with four new branches: pip base requirements, pip plugin requirements, git fetch + hard reset, and recursive __pycache__ deletion. Adds GET /system/git-info returning branch, dirty state, recent commits, and scrubbed remote URL.
Tools tab routing and partial
web_interface/blueprints/pages_v3.py, web_interface/templates/v3/base.html, web_interface/templates/v3/partials/tools.html
pages_v3.py adds _load_tools_partial() with TemplateNotFound/OSError error handling. base.html adds the Tools nav button and HTMX lazy-load panel with a non-HTMX fallback. tools.html renders Git, dependency, maintenance, and services sections; includes toolsAction(), force-reset confirmation toggles, and a loadGitInfo() panel.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • ChuckBuilds/LEDMatrix#337: Also modifies save_main_config display-form handling in api_v3.py to persist new hardware settings at the same function level.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 76.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly captures the two main changes: the new Tools tab and the row address type display setting.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/tools-tab-and-row-address-type

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codacy-production

codacy-production Bot commented Jun 16, 2026

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 5 complexity · 0 duplication

Metric Results
Complexity 5
Duplication 0

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@web_interface/blueprints/api_v3.py`:
- Around line 1674-1684: The git subprocess commands (branch, status, log, and
remote) are not checking return codes before using their output. If any command
fails, the empty stdout is silently treated as a clean state (dirty: false,
empty strings). Check the returncode attribute of each subprocess.run() result
and if any command fails (returncode != 0), return an error response with a
clear message explaining which git command failed, rather than proceeding with
empty/blank values that misrepresent the repository state.
- Around line 1591-1599: The subprocess output from pip operations is unbounded
and can consume significant RAM on Raspberry Pi, especially with verbose
dependency resolution or build failures. Create a shared helper function that
captures only the last N bytes or lines of the combined stdout and stderr, and
includes the command, return code, and timeout context in the output. Apply this
helper to limit the output in the jsonify response's output field in both the
pip install operation at lines 1591-1599 (where subprocess.run is called and the
output field is populated in the jsonify response) and the similar pip operation
at lines 1607-1615 (which has an equivalent unbounded output field in its
jsonify response), replacing the direct concatenation of result.stdout and
result.stderr with calls to this new helper function.
- Around line 1600-1602: In the elif block for the 'install_plugin_requirements'
action, change the reference from the module-level plugin_manager to
api_v3.plugin_manager in the ternary expression on line 1601. This ensures
consistency with how plugin managers are accessed throughout the rest of the
blueprint and prevents inadvertently falling back to the PROJECT_ROOT /
'plugin-repos' default when only the blueprint attribute is initialized. Replace
plugin_manager with api_v3.plugin_manager in the condition that determines which
plugins directory to use.
- Around line 1677-1683: The remote_url field in the jsonify return statement is
including the raw output from git remote get-url, which may contain embedded
credentials in HTTPS URLs (e.g., https://user:password@github.com/repo.git).
Before including remote.stdout.strip() as the remote_url value in the response,
parse the URL using a library like urllib.parse to remove any user
authentication information, or extract only the safe components (protocol, host,
path) and reconstruct the URL without credentials. This prevents accidental
credential exposure in the API response.
- Around line 1640-1646: The `clear_pycache` action increments the `cleared`
counter unconditionally even though `shutil.rmtree()` is called with
`ignore_errors=True`, meaning failed deletions are still counted as successful.
Fix this by removing `ignore_errors=True` from the shutil.rmtree call in the
rglob loop and wrapping it in a try-except block. Only increment the `cleared`
counter when deletion succeeds, and track any failures in a separate counter.
Return both the cleared count and failed count (or a descriptive error message)
in the jsonify response so users get accurate feedback about what was actually
deleted versus what failed.
- Around line 1625-1633: The git commands in the subprocess.run calls rely on
PATH lookup without verifying git is available, which is inconsistent with the
existing pattern used for _SUDO and _JOURNALCTL. Add _GIT = shutil.which('git')
near the top of the module alongside the other tool resolution constants, then
validate that _GIT is not None at the start of both execute_system_action() and
get_git_info() functions by returning a jsonify response with status 503 if the
git command is unavailable, and finally replace all instances of 'git' strings
in the subprocess.run() calls within these endpoints with the resolved _GIT
variable path.

In `@web_interface/blueprints/pages_v3.py`:
- Around line 457-459: The exception handler for the Tools partial uses an
overly broad `except Exception` clause and lacks debugging context for remote
Raspberry Pi environments. Replace the broad `except Exception` with specific
template-related exceptions (such as TemplateNotFound, TemplateError, or other
Jinja2/Flask template exceptions) to catch only relevant errors. Update the
logger.error call to include the `[Pages V3][Tools]` context prefix in the log
message to provide structured logging context for troubleshooting. Additionally,
update the returned error message string to also include the `[Pages V3][Tools]`
prefix for consistency in error responses, ensuring both logs and client-facing
messages provide clear context about which component failed.

In `@web_interface/templates/v3/base.html`:
- Around line 1298-1308: The Tools tab in the template relies solely on HTMX
loading via hx-get without any fallback mechanism, whereas other tabs have
fallback paths in the loadTabContent function. Add a non-HTMX fallback branch in
the loadTabContent function to handle the Tools tab (when activeTab === 'tools')
so that if HTMX fails or is disabled, the tab content can still load gracefully
instead of remaining blank. Ensure the fallback mimics how other tabs in
loadTabContent degrade gracefully.

In `@web_interface/templates/v3/partials/tools.html`:
- Around line 222-246: The toolsAction function makes unauthenticated requests
to the /api/v3/system/action endpoint which performs dangerous state-changing
operations. Add authentication to protect this endpoint by modifying the fetch
call in toolsAction to include an authentication token or credential in the
request headers, and update the corresponding backend execute_system_action
endpoint to validate this authentication before allowing any system action to
proceed. This ensures only authorized clients can trigger destructive operations
like git reset, pip install, or service restarts.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 68218690-be81-4288-811d-2645ea1c279f

📥 Commits

Reviewing files that changed from the base of the PR and between d297dd6 and b2524e9.

📒 Files selected for processing (8)
  • first_time_install.sh
  • scripts/install/one-shot-install.sh
  • scripts/install_dependencies_apt.py
  • web_interface/blueprints/api_v3.py
  • web_interface/blueprints/pages_v3.py
  • web_interface/templates/v3/base.html
  • web_interface/templates/v3/partials/display.html
  • web_interface/templates/v3/partials/tools.html

Comment thread web_interface/blueprints/api_v3.py
Comment thread web_interface/blueprints/api_v3.py
Comment thread web_interface/blueprints/api_v3.py
Comment thread web_interface/blueprints/api_v3.py Outdated
Comment thread web_interface/blueprints/api_v3.py Outdated
Comment thread web_interface/blueprints/api_v3.py Outdated
Comment thread web_interface/blueprints/pages_v3.py Outdated
Comment thread web_interface/templates/v3/base.html
Comment thread web_interface/templates/v3/partials/tools.html
Chuck and others added 2 commits June 29, 2026 11:11
Adds a Tools/Utilities tab to the web interface with one-click
maintenance buttons that previously required SSH:
- Git status panel (branch, dirty state, recent commits)
- Pull latest (rebase) and force reset to origin/main
- Reinstall base requirements (pip, with output)
- Reinstall per-plugin requirements (pass/fail per plugin)
- Clear __pycache__ directories
- Quick-access restart for display and web services

Also exposes the hzeller row_address_type option (0–4) in the
Display settings tab. The backend already read this value from
config; the UI, API field list, and validation were missing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add _GIT = shutil.which('git') alongside _SUDO/_JOURNALCTL; return
  503 in force_git_reset and get_git_info if git is unavailable
- Check git branch/status returncodes in get_git_info(); return a clear
  500 error instead of silently treating a failed run as a clean repo
- Cap pip stdout+stderr at 50 KB via _truncate_output() helper to
  avoid OOM on verbose dependency resolution or build failures
- Scrub embedded HTTPS credentials from remote_url via
  _scrub_git_remote_url() using urllib.parse before returning to UI
- Fix clear_pycache to track and report failed deletions separately
  instead of counting them as successes (removed ignore_errors=True,
  wrapped in try/except OSError)

Skipped: plugin_manager-vs-api_v3.plugin_manager (api_v3 is the
Blueprint object; accessing .plugin_manager on it would fail — module-
level variable is the correct pattern used throughout this blueprint);
pages_v3 broad-except (identical to every other _load_*_partial in the
file); base.html HTMX fallback (loadTabContent handles all tabs
generically; named fallbacks only exist for tabs needing JS re-init);
tools.html auth (pre-existing architectural decision — reboot/shutdown
on the same endpoint are also unauthenticated).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@ChuckBuilds ChuckBuilds force-pushed the feat/tools-tab-and-row-address-type branch from e5ae3cc to 344f64e Compare June 29, 2026 15:12
- api_v3: use getattr(api_v3, 'plugin_manager', None) instead of the
  module-level plugin_manager (always None); app.py sets the blueprint
  attribute, not the module global, so the fallback to plugin-repos was
  always taken
- pages_v3: replace broad except Exception in _load_tools_partial with
  specific TemplateNotFound / OSError handlers and add [Pages V3][Tools]
  context prefix to log messages and error responses for easier Pi
  debugging
- base.html: add Tools tab branch to the HTMX-unavailable fallback block
  in loadTabContent so the tab loads gracefully via direct fetch if HTMX
  never initialises

Skipped: auth on execute_system_action — pre-existing app-wide design;
reboot/shutdown and all other system actions share the same exposure.
An app-level auth layer is the correct fix and is out of scope here.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@web_interface/blueprints/api_v3.py`:
- Around line 1672-1690: The per-plugin install loop in the plugin requirements
handler is letting subprocess exceptions escape, which collapses the whole batch
into one generic failure. Update the logic around the subprocess.run call in the
plugins_dir loop to catch TimeoutExpired/OSError per plugin, append a failed
result entry with the plugin name and error text, and then continue iterating so
the existing results breakdown stays accurate. Keep the success/error summary
based on the accumulated results from this handler.
- Around line 825-838: The `double_sided_copies` validation in `api_v3.py` only
enforces the numeric range, but it also needs to match the selected hardware
dimension. Update the `ds_config` handling so that after parsing `copies`, it
checks divisibility against `chain_length` when `double_sided_axis` is
`horizontal` and against `parallel` when `double_sided_axis` is `vertical`; if
the value does not divide evenly, return a 400 error with a clear validation
message. Keep the existing `double_sided_axis` and range checks, and apply the
new logic in the same request path where `ds_config['copies']` is set.
- Around line 813-839: The double-sided form fields are being handled in the
display-specific block but still fall through to the generic config merge,
causing stray root-level keys to persist. Update the save flow in api_v3.py so
the double-sided handling in the current_config['display']['double_sided'] block
either removes double_sided_enabled, double_sided_copies, and double_sided_axis
from data after processing or explicitly excludes them from the later merge
loop. Use the existing double_sided_fields logic and the generic merge section
to ensure these keys are only stored under display.double_sided and never at the
config root.

In `@web_interface/templates/v3/partials/tools.html`:
- Around line 19-20: The “Pull latest (rebase)” tooltip in tools.html overstates
the behavior by promising stash restoration that the backend does not perform.
Update the copy for this action to match the actual git_pull flow: it stashes
local changes and runs git pull --rebase, but does not reapply the stash
afterward. Keep the wording aligned with the tool label and the git_pull
behavior so the UI no longer claims “then restores the stash.”
- Around line 266-299: The git info fetch flow in the panel rendering logic is
treating backend error responses as normal data, causing a false “clean” state
instead of showing the real failure. Update the fetch handling around r.json()
in the git-info block to check r.ok or an error status field before building the
badge and HTML. If the response indicates an error, render d.message in the
panel instead of deriving branch/dirty UI from the payload, and keep the
existing .catch path for transport failures.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 41c7d39d-3a41-404e-b813-ef0b53827eae

📥 Commits

Reviewing files that changed from the base of the PR and between b2524e9 and 06ea4aa.

📒 Files selected for processing (5)
  • web_interface/blueprints/api_v3.py
  • web_interface/blueprints/pages_v3.py
  • web_interface/templates/v3/base.html
  • web_interface/templates/v3/partials/display.html
  • web_interface/templates/v3/partials/tools.html
🚧 Files skipped from review as they are similar to previous changes (1)
  • web_interface/blueprints/pages_v3.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@web_interface/blueprints/api_v3.py`:
- Around line 1672-1690: The per-plugin install loop in the plugin requirements
handler is letting subprocess exceptions escape, which collapses the whole batch
into one generic failure. Update the logic around the subprocess.run call in the
plugins_dir loop to catch TimeoutExpired/OSError per plugin, append a failed
result entry with the plugin name and error text, and then continue iterating so
the existing results breakdown stays accurate. Keep the success/error summary
based on the accumulated results from this handler.
- Around line 825-838: The `double_sided_copies` validation in `api_v3.py` only
enforces the numeric range, but it also needs to match the selected hardware
dimension. Update the `ds_config` handling so that after parsing `copies`, it
checks divisibility against `chain_length` when `double_sided_axis` is
`horizontal` and against `parallel` when `double_sided_axis` is `vertical`; if
the value does not divide evenly, return a 400 error with a clear validation
message. Keep the existing `double_sided_axis` and range checks, and apply the
new logic in the same request path where `ds_config['copies']` is set.
- Around line 813-839: The double-sided form fields are being handled in the
display-specific block but still fall through to the generic config merge,
causing stray root-level keys to persist. Update the save flow in api_v3.py so
the double-sided handling in the current_config['display']['double_sided'] block
either removes double_sided_enabled, double_sided_copies, and double_sided_axis
from data after processing or explicitly excludes them from the later merge
loop. Use the existing double_sided_fields logic and the generic merge section
to ensure these keys are only stored under display.double_sided and never at the
config root.

In `@web_interface/templates/v3/partials/tools.html`:
- Around line 19-20: The “Pull latest (rebase)” tooltip in tools.html overstates
the behavior by promising stash restoration that the backend does not perform.
Update the copy for this action to match the actual git_pull flow: it stashes
local changes and runs git pull --rebase, but does not reapply the stash
afterward. Keep the wording aligned with the tool label and the git_pull
behavior so the UI no longer claims “then restores the stash.”
- Around line 266-299: The git info fetch flow in the panel rendering logic is
treating backend error responses as normal data, causing a false “clean” state
instead of showing the real failure. Update the fetch handling around r.json()
in the git-info block to check r.ok or an error status field before building the
badge and HTML. If the response indicates an error, render d.message in the
panel instead of deriving branch/dirty UI from the payload, and keep the
existing .catch path for transport failures.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 41c7d39d-3a41-404e-b813-ef0b53827eae

📥 Commits

Reviewing files that changed from the base of the PR and between b2524e9 and 06ea4aa.

📒 Files selected for processing (5)
  • web_interface/blueprints/api_v3.py
  • web_interface/blueprints/pages_v3.py
  • web_interface/templates/v3/base.html
  • web_interface/templates/v3/partials/display.html
  • web_interface/templates/v3/partials/tools.html
🚧 Files skipped from review as they are similar to previous changes (1)
  • web_interface/blueprints/pages_v3.py
🛑 Comments failed to post (5)
web_interface/blueprints/api_v3.py (3)

813-839: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Do not persist double_sided_* form fields at the config root.

These keys are written into current_config['display']['double_sided'] here, but they are never removed from data and the generic merge loop at Lines 1070-1090 does not skip them. The next save will therefore also write double_sided_enabled, double_sided_copies, and double_sided_axis as stray top-level config keys.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web_interface/blueprints/api_v3.py` around lines 813 - 839, The double-sided
form fields are being handled in the display-specific block but still fall
through to the generic config merge, causing stray root-level keys to persist.
Update the save flow in api_v3.py so the double-sided handling in the
current_config['display']['double_sided'] block either removes
double_sided_enabled, double_sided_copies, and double_sided_axis from data after
processing or explicitly excludes them from the later merge loop. Use the
existing double_sided_fields logic and the generic merge section to ensure these
keys are only stored under display.double_sided and never at the config root.

825-838: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Validate double_sided_copies against the selected hardware dimension.

The UI now says the copy count “must divide the panel evenly”, but the backend only checks 2..8. Values like copies=2 with parallel=1 or copies=3 with chain_length=2 will still save and produce an impossible layout. Please enforce divisibility against chain_length for horizontal and parallel for vertical.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web_interface/blueprints/api_v3.py` around lines 825 - 838, The
`double_sided_copies` validation in `api_v3.py` only enforces the numeric range,
but it also needs to match the selected hardware dimension. Update the
`ds_config` handling so that after parsing `copies`, it checks divisibility
against `chain_length` when `double_sided_axis` is `horizontal` and against
`parallel` when `double_sided_axis` is `vertical`; if the value does not divide
evenly, return a 400 error with a clear validation message. Keep the existing
`double_sided_axis` and range checks, and apply the new logic in the same
request path where `ds_config['copies']` is set.

1672-1690: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Keep per-plugin pip failures local to the failing plugin.

A single TimeoutExpired/OSError here escapes to the outer handler and turns the whole batch into one generic failure, so the Tools tab loses the per-plugin pass/fail breakdown it promises. Catch subprocess errors inside the loop, append a failed entry for that plugin, and continue with the rest. As per coding guidelines, “Implement graceful degradation to continue operation when non-critical features fail” and “Provide clear error messages for troubleshooting”.

🧰 Tools
🪛 ast-grep (0.44.0)

[error] 1675-1678: Command coming from incoming request
Context: subprocess.run(
[sys.executable, '-m', 'pip', 'install', '--break-system-packages', '-r', str(req)],
capture_output=True, text=True, timeout=60
)
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(subprocess-from-request)

🪛 Ruff (0.15.18)

[error] 1676-1676: subprocess call: check for execution of untrusted input

(S603)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web_interface/blueprints/api_v3.py` around lines 1672 - 1690, The per-plugin
install loop in the plugin requirements handler is letting subprocess exceptions
escape, which collapses the whole batch into one generic failure. Update the
logic around the subprocess.run call in the plugins_dir loop to catch
TimeoutExpired/OSError per plugin, append a failed result entry with the plugin
name and error text, and then continue iterating so the existing results
breakdown stays accurate. Keep the success/error summary based on the
accumulated results from this handler.

Source: Coding guidelines

web_interface/templates/v3/partials/tools.html (2)

19-20: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Do not promise stash restoration unless the backend actually reapplies it.

The new copy says this flow “then restores the stash”, but git_pull only does git stash push before the pull and never pops/applies it afterward. That makes the UI promise materially wrong for anyone updating with local changes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web_interface/templates/v3/partials/tools.html` around lines 19 - 20, The
“Pull latest (rebase)” tooltip in tools.html overstates the behavior by
promising stash restoration that the backend does not perform. Update the copy
for this action to match the actual git_pull flow: it stashes local changes and
runs git pull --rebase, but does not reapply the stash afterward. Keep the
wording aligned with the tool label and the git_pull behavior so the UI no
longer claims “then restores the stash.”

266-299: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Handle /system/git-info error payloads before rendering a clean state.

This code calls r.json() unconditionally and then derives the badge from d.dirty, so a 503/500 JSON response from the backend currently renders as unknown + clean instead of showing the actual error. Check r.ok or d.status === 'error' first and surface d.message in the panel. As per coding guidelines, “Provide user-friendly error messages that explain what went wrong and potential solutions”.

🧰 Tools
🪛 ast-grep (0.44.0)

[warning] 295-295: Avoid assigning untrusted data to innerHTML/outerHTML or document.write
Context: panel.innerHTML = html
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting').

(inner-outer-html)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web_interface/templates/v3/partials/tools.html` around lines 266 - 299, The
git info fetch flow in the panel rendering logic is treating backend error
responses as normal data, causing a false “clean” state instead of showing the
real failure. Update the fetch handling around r.json() in the git-info block to
check r.ok or an error status field before building the badge and HTML. If the
response indicates an error, render d.message in the panel instead of deriving
branch/dirty UI from the payload, and keep the existing .catch path for
transport failures.

Source: Coding guidelines

- Wrap per-plugin subprocess.run in try/except TimeoutExpired/OSError so
  one plugin's failure appends a result entry and continues the loop
  rather than collapsing the whole batch into a 500
- Validate double_sided_copies divisibility against chain_length
  (horizontal axis) or parallel (vertical axis) after the range check;
  reads effective axis from the current request or stored config
- Exclude double_sided_fields from the generic key-merge loop so
  double_sided_enabled/copies/axis are never written as root-level keys
- Fix tools.html copy: "then restores the stash" removed — git_pull
  stashes changes but never pops them
- Check r.ok and d.status in loadGitInfo before building the panel;
  backend error messages now surface instead of silently showing a
  false-clean state

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Comment thread web_interface/blueprints/api_v3.py Fixed
CodeQL flagged str(exc) flowing into the JSON response for the
install_plugin_requirements action. Use exc.strerror instead, which
gives the OS error description ("No such file or directory",
"Permission denied") without the internal filesystem path.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@ChuckBuilds ChuckBuilds merged commit 6096a22 into main Jun 29, 2026
7 checks passed
ChuckBuilds added a commit that referenced this pull request Jun 30, 2026
The `else if (++tries > 100)` block added by #373 was missing its
closing `}`, leaving the setInterval arrow function syntactically
unclosed. This caused a JS parse error that silenced the entire
1400-line script block — including the EventSource setup — so the
connection-status indicator never left its default "Disconnected"
state for all users after updating.

Co-authored-by: Chuck <chuck@example.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants